﻿////////////////////////////////////////////////////////////////////////////////////////////////////
//
// VSCoverageToEmma - Visual Studio Coverage file to Emma converter
// Copyright (c) Kouji Matsui, All rights reserved.
//
// * Redistribution and use in source and binary forms, with or without modification,
//   are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright notice,
//   this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
//   this list of conditions and the following disclaimer in the documentation
//   and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
// IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
////////////////////////////////////////////////////////////////////////////////////////////////////

using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Xml;
using VSCoverageToEmma.Analyzer.DataSetViews;
using VSCoverageToEmma.Analyzer.EmmaViews;

namespace VSCoverageToEmma.Analyzer
{
	internal sealed class EmmaAnalyzer : EmmaSourceFileBase<EmmaPackage, EmmaSourceFile>
	{
		public EmmaAnalyzer(string name, DataSet dataSet)
			: base(name)
		{
			// First step: Interprete and remapping structual view classes (DataSetView)
			//   --> Lines rows multiple definitions by one method row, therefore group by methods.
			//   --> Tables are independent of other tables, apply parallel execution.

			Dictionary<string, DataSetNamespace> dataSetNamespaces = null;
			Dictionary<string, DataSetClass> dataSetClasses = null;
			Dictionary<string, DataSetMethod> dataSetMethods = null;
			Dictionary<string, IEnumerable<DataSetLine>> dataSetLines = null;
			Dictionary<int, DataSetSourceFile> dataSetSourceFiles = null;

			Parallel.Invoke(
				() => dataSetNamespaces =
					dataSet.Tables["NamespaceTable"].Rows.
						Cast<DataRow>().
						Select(row => new DataSetNamespace(row)).
						ToDictionary(entry => entry.KeyName, entry => entry),

				() => dataSetClasses =
					dataSet.Tables["Class"].Rows.
						Cast<DataRow>().
						Select(row => new DataSetClass(row)).
						ToDictionary(entry => entry.KeyName, entry => entry),

				() => dataSetMethods =
					dataSet.Tables["Method"].Rows.
						Cast<DataRow>().
						Select(row => new DataSetMethod(row)).
						ToDictionary(entry => entry.KeyName, entry => entry),

				() => dataSetLines =
					dataSet.Tables["Lines"].Rows.
						Cast<DataRow>().
						Select(row => new DataSetLine(row)).
						GroupBy(entry => entry.MethodKeyName).
						ToDictionary(g => g.Key, g => g.AsEnumerable()),

				() => dataSetSourceFiles =
					dataSet.Tables["SourceFileNames"].Rows.
						Cast<DataRow>().
						Select(row => new DataSetSourceFile(row)).
						ToDictionary(entry => entry.SourceFileID, entry => entry));

			// Step two: Construct view structures.
			//   --> Node order: NamespaceTable - Class - Method - Lines
			//   --> Nodes are independent of other nodes, apply parallel execution.

			Parallel.Invoke(
				() =>
				{
					foreach (var cls in dataSetClasses.Values)
					{
						DataSetNamespace ns;
						if (dataSetNamespaces.TryGetValue(cls.NamespaceKeyName, out ns) == true)
						{
							ns.Add(cls);
						}
					}
				},

				() =>
				{
					foreach (var method in dataSetMethods.Values)
					{
						DataSetClass cls;
						if (dataSetClasses.TryGetValue(method.ClassKeyName, out cls) == true)
						{
							cls.Add(method);
						}
					}
				},

				() =>
				{
					foreach (var entry in dataSetLines)
					{
						DataSetMethod method;
						if (dataSetMethods.TryGetValue(entry.Key, out method) == true)
						{
							foreach (var line in entry.Value)
							{
								method.Add(line);
							}
						}
					}
				});

			this.ConstructEmmaViews(dataSetNamespaces, dataSetSourceFiles);
		}

		private void ConstructEmmaViews(
			Dictionary<string, DataSetNamespace> dataSetNamespaces,
			Dictionary<int, DataSetSourceFile> dataSetSourceFiles)
		{
			foreach (var dataSetNamespace in dataSetNamespaces.Values)
			{
				EmmaPackage emmaPackage;
				if (children_.TryGetValue(dataSetNamespace.Name, out emmaPackage) == false)
				{
					emmaPackage = new EmmaPackage(dataSetNamespace.Name);
					children_.Add(dataSetNamespace.Name, emmaPackage);
				}

				// .NET class contains the partial class, must be aggregate sourceID by namespaces.
				foreach (var dataSetClass in dataSetNamespace)
				{
					foreach (var dataSetMethod in dataSetClass)
					{
						EmmaMethod emmaMethod = null;

						foreach (var dataSetLine in dataSetMethod)
						{
							// Require source file record
							DataSetSourceFile dataSetSourceFile;
							if (dataSetSourceFiles.TryGetValue(dataSetLine.SourceFileID, out dataSetSourceFile) == true)
							{
								var sourceFileName = Path.GetFileName(dataSetSourceFile.SourceFilePath);

								var emmaSourceFile = emmaPackage.GetOrAdd(sourceFileName, fileName => new EmmaSourceFile(fileName));
								var emmaClass = emmaSourceFile.GetOrAdd(dataSetClass.Name, className => new EmmaClass(className));
								emmaMethod = emmaClass.GetOrAdd(dataSetMethod.Name, methodName => new EmmaMethod(methodName));
							}
						}

						if (emmaMethod != null)
						{
							emmaMethod.Blocks.Add(
								dataSetMethod.BlocksCovered,
								dataSetMethod.BlocksCovered + dataSetMethod.BlocksNotCovered);
							emmaMethod.Lines.Add(
								dataSetMethod.LinesCovered,
								dataSetMethod.LinesCovered + dataSetMethod.LinesPartiallyCovered + dataSetMethod.LinesNotCovered);
						}
					}
				}
			}
		}

		public EmmaContains Packages
		{
			get
			{
				return new EmmaContains(
					children_.Select(entry => entry.Value.Packages.ValidCount).Sum(),
					children_.Select(entry => entry.Value.Packages.TotalCount).Sum());
			}
		}

		public EmmaContains SourceFiles
		{
			get
			{
				return new EmmaContains(
					children_.Select(entry => entry.Value.SourceFiles.ValidCount).Sum(),
					children_.Select(entry => entry.Value.SourceFiles.TotalCount).Sum());
			}
		}

		protected override void WriteChildren(XmlWriter xw)
		{
			this.WriteChildren(xw, "package");
		}
	}
}
